package com.woniuxy.service.impl;

import com.github.pagehelper.PageInfo;
import com.woniuxy.entity.dto.DictionaryDTO;
import com.woniuxy.entity.dto.stationPile.PileSimpleDTO;
import com.woniuxy.entity.dto.stationPile.StationDetailDTO;
import com.woniuxy.entity.dto.stationPile.StationDisplayDTO;
import com.woniuxy.entity.dto.stationPile.StationInfoDTO;
import com.woniuxy.entity.dto.stationPile.StationMapInfoDTO;

import com.woniuxy.entity.po.stationPile.Station;
import com.woniuxy.entity.po.stationPile.StationImg;
import com.woniuxy.entity.po.stationPile.StationSchedule;
import com.woniuxy.mapper.PileMapper;
import com.woniuxy.mapper.StationImgMapper;
import com.woniuxy.mapper.StationMapper;
import com.woniuxy.mapper.StationScheduleMapper;
import com.woniuxy.service.StationService;
import com.woniuxy.util.*;
import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.woniuxy.util.RedisKeyConst.*;

@Service
@AllArgsConstructor
public class StationServiceImpl implements StationService {
    // Redis缓存过期时间常量
    private static final long STATION_CACHE_EXPIRE_DAYS = 7;
    private static final long DICTIONARY_CACHE_EXPIRE_DAYS = 20;

    private StationMapper stationMapper;
    private PileMapper pileMapper;
    private StationImgMapper stationImgMapper;
    private StationScheduleMapper stationScheduleMapper;
    private PageHelperUtil pageHelperUtil;
    private RedisTemplate<String, Object> redisTemplate;
    private CodeUtil codeUtil;
    private MinioUtils minioUtils;


    /**
     * @Description:展示所有列表
     * @Param: [pageParam]
     * @return: com.github.pagehelper.PageInfo<?>
     * @Author: Destbin
     * @Date: 2025/3/28
     */
    @Override
    public PageInfo<StationDisplayDTO> queryAll(PageParam<?> pageParam) {
        return pageHelperUtil.getPageInfo(pageParam, stationMapper::queryAll, StationDisplayDTO::new);
    }

    /**
     * @Description: 条件查询展示列表
     * @Param: [pageParam]
     * @return: com.github.pagehelper.PageInfo<?>
     * @Author: Destbin
     * @Date: 2025/3/28
     */
    @Override
    public PageInfo<StationDisplayDTO> queryInfo(PageParam<StationInfoDTO> pageParam) {
        return pageHelperUtil.getPageInfo(pageParam, stationMapper::queryInfo, StationDisplayDTO::new);
    }

    /**
     * @Description:检验合法性后,删除充电站充电桩与它们的图片表
     * @Param: [id]
     * @Author: Destbin
     * @Date: 2025/3/28
     */
    @Override
    @Transactional
    public void del(Long id) {
        // 查询充电站下是否存在已关联的禁用以外的充电桩
        if (pileMapper.hasActivePiles(id) != null) {
            throw new RuntimeException("该充电站下存在已关联的启用的充电桩，请先禁用或删除关联的充电桩");
        }
        // 删除充电站充电桩与它们的图片表
        stationMapper.delStationAndPileAndImg(id);

        // 更新地图信息缓存 - 只操作相关数据
        // 1. 从站点ID集合中移除（使用Set原子操作）
        redisTemplate.opsForSet().remove(STATION_MAP_IDS_KEY, id);

        // 2. 从Hash中移除站点信息
        redisTemplate.opsForHash().delete(STATION_MAP_INFO_KEY, String.valueOf(id));

        // 3. 删除站点详情缓存
        redisTemplate.opsForHash().delete(STATION_DETAIL_KEY, String.valueOf(id));

        // 刷新过期时间
        refreshExpireTime(STATION_MAP_IDS_KEY, STATION_CACHE_EXPIRE_DAYS);
    }

    @Override
    @Transactional
    public void batchDel(List<Long> ids) {
        // 查询充电站下是否存在已关联的禁用以外的充电桩
        if (pileMapper.batchHasActivePiles(ids) != null) {
            throw new RuntimeException("该充电站下存在已关联的启用的充电桩，请先禁用或删除关联的充电桩");
        }
        // 删除充电站充电桩与它们的图片表
        stationMapper.batchDelStationAndPileAndImg(ids);

        // 更新地图信息缓存 - 批量移除相关数据
        // 1. 从站点ID集合中批量移除
        redisTemplate.opsForSet().remove(STATION_MAP_IDS_KEY, ids.toArray());

        // 2. 从Hash中批量移除站点信息
        for (Long id : ids) {
            String idStr = String.valueOf(id);
            redisTemplate.opsForHash().delete(STATION_MAP_INFO_KEY, idStr);

            // 3. 同时删除站点详情缓存
            redisTemplate.opsForHash().delete(STATION_DETAIL_KEY, idStr);
        }

        // 刷新过期时间
        refreshExpireTime(STATION_MAP_IDS_KEY, STATION_CACHE_EXPIRE_DAYS);
    }

    @Override
    public List<StationMapInfoDTO> queryAllForMap() {
        HashOperations<String, String, Object> hashOps = redisTemplate.opsForHash();
        SetOperations<String, Object> setOps = redisTemplate.opsForSet();

        // 获取所有站点ID集合（从Set中获取）
        Set<Object> stationIdSet = setOps.members(STATION_MAP_IDS_KEY);

        if (stationIdSet == null || stationIdSet.isEmpty()) {
            // 如果站点ID集合不存在或为空，则需要完全重建缓存
            return rebuildFullMapCache();
        } else {
            // 如果站点ID集合存在，则根据ID集合获取各站点详情
            List<StationMapInfoDTO> result = new ArrayList<>(stationIdSet.size());
            List<Long> missingIds = new ArrayList<>();

            // 从缓存中获取已有的站点信息
            for (Object idObj : stationIdSet) {
                Long id = convertToLong(idObj);
                if (id == null) continue;

                String stationKey = String.valueOf(id);
                StationMapInfoDTO station = (StationMapInfoDTO) hashOps.get(STATION_MAP_INFO_KEY, stationKey);

                if (station != null) {
                    result.add(station);
                } else {
                    // 记录缓存中缺失的站点ID
                    missingIds.add(id);
                }
            }

            // 如果有缺失的站点信息，则单独查询并添加到缓存
            if (!missingIds.isEmpty()) {
                // 查询缺失的站点信息
                List<StationMapInfoDTO> missingStations = fetchMissingStations(missingIds);

                // 添加到结果集
                result.addAll(missingStations);

                // 缓存这些站点信息
                for (StationMapInfoDTO station : missingStations) {
                    hashOps.put(STATION_MAP_INFO_KEY, String.valueOf(station.getId()), station);
                }

                // 刷新过期时间
                refreshExpireTime(STATION_MAP_INFO_KEY, STATION_CACHE_EXPIRE_DAYS);
            }

            return result;
        }
    }

    /**
     * 完全重建地图缓存
     */
    private List<StationMapInfoDTO> rebuildFullMapCache() {
        HashOperations<String, String, Object> hashOps = redisTemplate.opsForHash();
        SetOperations<String, Object> setOps = redisTemplate.opsForSet();

        // 查询所有站点信息
        List<StationMapInfoDTO> allStations = ListValueConvertUtils.listConvert(
                stationMapper::queryAllForMap, StationMapInfoDTO::new);

        if (allStations != null && !allStations.isEmpty()) {
            // 先清除旧缓存
            redisTemplate.delete(STATION_MAP_IDS_KEY);

            // 准备批量添加到Set集合的ID数组
            Object[] stationIds = allStations.stream()
                    .map(StationMapInfoDTO::getId)
                    .toArray();

            // 使用Set原子操作添加所有ID
            setOps.add(STATION_MAP_IDS_KEY, stationIds);

            // 缓存每个站点的详细信息
            for (StationMapInfoDTO station : allStations) {
                String stationKey = String.valueOf(station.getId());
                hashOps.put(STATION_MAP_INFO_KEY, stationKey, station);
            }

            // 设置过期时间
            redisTemplate.expire(STATION_MAP_IDS_KEY, STATION_CACHE_EXPIRE_DAYS, TimeUnit.DAYS);
            redisTemplate.expire(STATION_MAP_INFO_KEY, STATION_CACHE_EXPIRE_DAYS, TimeUnit.DAYS);
        }

        return allStations != null ? allStations : new ArrayList<>();
    }

    /**
     * 查询缺失的站点信息
     */
    private List<StationMapInfoDTO> fetchMissingStations(List<Long> missingIds) {
        // 数据库提供根据ID列表批量查询的方法
        return stationMapper.queryStationsByIds(missingIds).stream()
                .map(StationMapInfoDTO::new)
                .collect(Collectors.toList());
    }

    @Override
    public List<DictionaryDTO> queryStationTypeList() {
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();

        // 尝试从Redis获取数据
        Map<Object, Object> entries = hash.entries(STATION_TYPE_KEY);
        if (!entries.isEmpty()) {
            return entries.entrySet().stream()
                    .map(entry -> {
                        DictionaryDTO dto = new DictionaryDTO();
                        dto.setId(Long.parseLong((String) entry.getKey()));
                        dto.setTypeValue((String) entry.getValue());
                        return dto;
                    })
                    .collect(Collectors.toList());
        }

        // Redis中没有数据，从数据库查询
        List<DictionaryDTO> dictionaryDTOS = ListValueConvertUtils.listConvert(
                stationMapper::queryStationTypeList,
                DictionaryDTO::new);

        // 存入Redis
        if (!dictionaryDTOS.isEmpty()) {
            dictionaryDTOS.forEach(dto -> hash.put(STATION_TYPE_KEY, dto.getId().toString(), dto.getTypeValue()));
            redisTemplate.expire(STATION_TYPE_KEY, DICTIONARY_CACHE_EXPIRE_DAYS, TimeUnit.DAYS);
        }

        return dictionaryDTOS;
    }

    @Override
    public StationDetailDTO queryBasicInfo(Long id) {
        String cacheKey = String.valueOf(id);

        // 1. 尝试从Redis获取基本信息
        StationDetailDTO stationDetailDTO = (StationDetailDTO) redisTemplate.opsForHash().get(STATION_DETAIL_KEY, cacheKey);

        // 2. 如果Redis中没有数据，则查询基本信息并缓存
        if (stationDetailDTO == null) {
            Station station = stationMapper.queryStationDetail(id);
            if (station == null) {
                return null; // 站点不存在
            }
            stationDetailDTO = new StationDetailDTO(station);

            // 存入Redis缓存并设置过期时间
            redisTemplate.opsForHash().put(STATION_DETAIL_KEY, cacheKey, stationDetailDTO);
            refreshExpireTime(STATION_DETAIL_KEY, DICTIONARY_CACHE_EXPIRE_DAYS);
        }

        return stationDetailDTO;
    }

    @Override
    @Transactional
    public void addStation(Station station) {
        // 1 生成充电站编号
        String stationNum = codeUtil.generateRegularCode("STATION");
        station.setStationNum(stationNum);
        // 2 插入数据库
        stationMapper.addStation(station);
        // 3 获取充电站id
        Long stationId = station.getId();
        // 5 插入充电站图片信息
        if (station.getImgSrc() != null) {
            batchInsertStationImgs(station, stationId);
        }
        // 6 插入充电站规则信息
        List<StationSchedule> schedules = station.getSchedules().stream()
                .peek(schedule -> schedule.setStationId(stationId)).collect(Collectors.toList());
        stationScheduleMapper.batchInsert(schedules);

        // 更新地图缓存中的ID集合
        updateStationIdCache(stationId);
    }

    @Override
    @Transactional
    public void updateStation(Station station, List<MultipartFile> newImages, List<String> existingUrls) {
        // 更新主表
        stationMapper.updateById(station);
        // 查询旧图
        List<StationImg> oldImages = stationImgMapper.selectByStationId(station.getId());
        // 收集需要删除的图片ID
        List<Long> deleteIds = oldImages.stream()
                .filter(img -> !existingUrls.contains(img.getImgSrc()))
                .map(StationImg::getId)
                .collect(Collectors.toList());
        // 批量删除
        if (!deleteIds.isEmpty()) {
            stationImgMapper.batchDel(deleteIds);
        }
        // 获取充电站id
        Long stationId = station.getId();
        //初始化station的imgSrc
        station.setImgSrc(new ArrayList<>());
        // 添加新图
        if (!CollectionUtils.isEmpty(newImages)) {
            for (MultipartFile image : newImages) {
                try {
                    String imgUrl = minioUtils.uploadFile(image);
                    if (imgUrl != null) {
                        station.getImgSrc().add(imgUrl);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    // 考虑添加日志或抛出自定义异常
                }
            }
        }

        // 只有当有图片需要插入时才调用
        if (!CollectionUtils.isEmpty(station.getImgSrc())) {
            batchInsertStationImgs(station, stationId);
        }

        //删除旧规则
        stationScheduleMapper.delByStationId(stationId);
        //  插入充电站规则信息
        List<StationSchedule> schedules = station.getSchedules().stream()
                .peek(schedule -> schedule.setStationId(stationId)).collect(Collectors.toList());
        stationScheduleMapper.batchInsert(schedules);

        // 删除Redis缓存
        String cacheKey = String.valueOf(stationId);
        // 删除站点详情缓存
        redisTemplate.opsForHash().delete(STATION_DETAIL_KEY, cacheKey);
        // 删除地图信息缓存
        redisTemplate.opsForHash().delete(STATION_MAP_INFO_KEY, cacheKey);
    }

    private void batchInsertStationImgs(Station station, Long stationId) {
        List<StationImg> stationImgs = station.getImgSrc().stream()
                .map(imgSrc -> {
                    StationImg stationImg = new StationImg();
                    stationImg.setStationId(stationId);
                    stationImg.setImgSrc(imgSrc);
                    return stationImg;
                }).collect(Collectors.toList());
        stationImgMapper.batchInsert(stationImgs);
    }

    /**
     * 更新站点ID缓存（使用Set集合原子操作）
     */
    private void updateStationIdCache(Long stationId) {
        // 使用Set的add操作，原子性添加，自动去重
        redisTemplate.opsForSet().add(STATION_MAP_IDS_KEY, stationId);

        // 刷新过期时间
        refreshExpireTime(STATION_MAP_IDS_KEY, STATION_CACHE_EXPIRE_DAYS);
    }

    /**
     * 智能刷新过期时间（只在必要时设置）
     */
    private void refreshExpireTime(String key, long days) {
        Long ttl = redisTemplate.getExpire(key, TimeUnit.DAYS);
        // 如果键不存在或剩余时间不足预期的1/3，则刷新过期时间
        if (ttl == null || ttl < 0 || ttl < days / 3) {
            redisTemplate.expire(key, days, TimeUnit.DAYS);
        }
    }

    /**
     * 安全转换Object到Long
     */
    private Long convertToLong(Object obj) {
        if (obj == null) {
            return null;
        }

        if (obj instanceof Long) {
            return (Long) obj;
        }

        try {
            return Long.valueOf(obj.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }
}
